import requests
from conf import Conf, YamlConf, generate_conf
from model import Item
from rule import Rule
from model import Base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import argparse
from apscheduler.triggers.cron import CronTrigger
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_EXECUTED
import logging
from datetime import datetime
from functools import wraps

tags = {
    "PRODUCTID": "产品ID",
    "PRODUCTNAME": "产品名称",
    "DELIVERYMONTH": "交割月份",
    "PRESETTLEMENTPRICE": "前结算",
    "OPENPRICE": "今开盘",
    "HIGHESTPRICE": "最高价",
    "LOWESTPRICE": "最低价	",
    "CLOSEPRICE": "收盘价",
    "SETTLEMENTPRICE": "结算参考价",
    "ZD1_CHG": "涨跌1",
    "ZD2_CHG": "涨跌2",
    "VOLUME": "成交手",
    "TURNOVER": "成交额",
    "OPENINTEREST": "持仓手",
    "OPENINTERESTCHG": "变化",
}
# db
db = None
session_factory = None
# config log
logger = logging.getLogger("shfe")
file_handler = logging.FileHandler("shfe.log", encoding="utf-8")
file_handler.setFormatter(
    logging.Formatter(fmt="%(asctime)s %(levelname)s %(filename)s %(lineno)d %(process)d %(message)s",
                      datefmt="%Y-%m-%d %H:%M:%S"))
logger.addHandler(file_handler)
logger.setLevel(logging.DEBUG)


# 初始化decorator
def before_start(callback_func=None):
    def wrapper(func):
        @wraps(func)
        def inner(settings: dict):
            if callable(callback_func):
                callback_func(settings)
            return func(settings)

        return inner

    return wrapper


def find_rule(_id, rs):
    for rule in rs:
        if _id in rule.ids():
            return rule


# 过滤数据
def dispatch(items: Item, settings: dict):
    session = session_factory()
    # 根据Id找出对应rule，过滤数据
    rules = settings.get("rules")
    if not rules:
        for item in items:
            session.add(item)
            session.commit()

    else:
        for item in items:
            kv = {k.comment: getattr(item, k.name) for k in Item.__table__.columns}
            _id = getattr(item, "PRODUCT_ID")
            root = find_rule("*", rules)
            target = find_rule(_id, rules)
            mixed = set()
            conditions = []
            ref = None
            if target and root:
                ref = root.ref()
                mixed = root.conditions() | target.conditions()
            elif target:
                ref = target.ref()
                mixed = target.conditions()
            elif root:
                ref = root.ref()
                mixed = root.conditions()
            if mixed:
                for c in mixed:
                    if c.tag() in kv:
                        if kv[c.tag()] is not None:
                            conditions.append(c.symbol().verify(kv[c.tag()]))
            if conditions:
                # and
                if ref == "&":
                    if all(conditions):
                        session.add(item)
                        session.commit()
                # or
                else:
                    if any(conditions):
                        session.add(item)
                        session.commit()
            else:
                if root or target:
                    session.add(item)
                    session.commit()
    if session:
        session.close()


def work(settings: dict):
    default_header = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"
    }
    # json接口
    date = datetime.now().strftime("%Y%m%d")
    url = "https://www.shfe.com.cn/data/dailydata/kx/kx%s.dat" % date
    try:
        resp = requests.get(url, headers=default_header)
        resp.encoding = "utf-8"
        if resp.status_code != 200:
            logger.error("net err")
        else:
            items = []
            for r in resp.json()["o_curinstrument"]:
                if r["DELIVERYMONTH"].strip() == "小计":
                    continue
                items.append(Item(
                    PRODUCT_ID=r["PRODUCTID"].strip(),
                    PRODUCT_NAME=r["PRODUCTNAME"].strip(),
                    PRODUCT_MONTH=r["DELIVERYMONTH"].strip(),
                    PRE_SETTLEMENT_PRICE=r["PRESETTLEMENTPRICE"] if r["PRESETTLEMENTPRICE"] else 0,
                    OPEN_PRICE=r["OPENPRICE"] if r["OPENPRICE"] else 0,
                    HIGHEST_PRICE=r["HIGHESTPRICE"] if r["HIGHESTPRICE"] else 0,
                    LOWEST_PRICE=r["LOWESTPRICE"] if r["LOWESTPRICE"] else 0,
                    CLOSE_PRICE=r["CLOSEPRICE"] if r["CLOSEPRICE"] else 0,
                    SETTLEMENT_PRICE=r["SETTLEMENTPRICE"] if r["SETTLEMENTPRICE"] else 0,
                    ZD1_CHG=r["ZD1_CHG"] if r["ZD1_CHG"] else 0,
                    ZD2_CHG=r["ZD2_CHG"] if r["ZD2_CHG"] else 0,
                    VOLUME=r["VOLUME"] if r["VOLUME"] else 0,
                    TURNOVER=r["TURNOVER"] if r["TURNOVER"] else 0,
                    OPEN_INTEREST=r["OPENINTEREST"] if r["OPENINTEREST"] else 0,
                    OPEN_INTEREST_CHG=r["OPENINTERESTCHG"] if r["OPENINTERESTCHG"] else 0,
                ))
            logger.info("fetch success data size %d", len(items))
            dispatch(items, settings)
    except Exception as e:
        logger.error(e)


def job_listener(evt):
    if evt.exception:
        logger.error(evt.exception)
    else:
        logger.info(evt)


def run(conf: Conf):
    sys = conf.get("sys")
    rules = conf.get("rules")
    db_url = sys.get("db_url")
    settings = {}
    # 导入rules
    if rules:
        rules = [Rule.from_map(r) for r in rules]
        settings.update({"rules": rules})
    global db, session_factory
    db = create_engine(db_url)
    Base.metadata.create_all(db)
    session_factory = sessionmaker(db)
    settings.update({"conf": conf})
    scheduler = BlockingScheduler()  # 阻塞调度器，会阻塞当前进程
    cron_trigger = CronTrigger.from_crontab(sys.get("cron"))
    scheduler.add_listener(job_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
    scheduler.add_job(work, cron_trigger, args=(settings,))  # 间隔1s执行一次now函数
    scheduler.start()


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("-c", "--config", help="自定义配置文件")
    parser.add_argument("--run", action="store_true", help="执行任务")
    parser.add_argument("--generate", action="store_true", help="生成默认配置文件")
    args = parser.parse_args()
    if args.generate:
        generate_conf()
        exit(0)
    if args.run:
        if not args.config:
            raise ValueError("need config file")
        # run
        run(YamlConf.load("shfe.yaml"))
